以下這個官方範例...
為什麼會忽然拿出官方範例呢?
主要原因是因為Forge2D跟Collision不一樣,它並不支援一般Component,而是支援自己獨有的BodyComponent,而BodyComponent有著很複雜的建構子...不會用啊!沒辦法用「將現有的Component改為繼承擴充BodyComponent後直接開始測試」,所以只好乖乖地拿出官方範例來研究了!
(謹記:不要以為將現有方案升級自新系統新框架很簡單,那都是套件開發者不負責任的自吹自擂。該升級時還是要升級,但千萬不要有「這看起來好像很輕鬆」的妄想。)
以下這個官方範例就是將整個視窗做成四面牆,然後有顆球會在裡面隨意的彈跳碰撞。
class MyGame4 extends Forge2DGame {
//FlowerComponent square = FlowerComponent();
@override
Future<void> onLoad() async {
await super.onLoad();
//square.width = 20;
//square.height = 20;
//world.add(square);
camera.viewport.add(FpsTextComponent());
world.add(Ball());
world.addAll(createBoundaries());
}
List<Component> createBoundaries() {
final visibleRect = camera.visibleWorldRect;
final topLeft = visibleRect.topLeft.toVector2();
final topRight = visibleRect.topRight.toVector2();
final bottomRight = visibleRect.bottomRight.toVector2();
final bottomLeft = visibleRect.bottomLeft.toVector2();
return [
Wall(topLeft, topRight),
Wall(topRight, bottomRight),
Wall(bottomLeft, bottomRight),
Wall(topLeft, bottomLeft),
];
}
}
class Ball extends BodyComponent with TapCallbacks {
Ball({Vector2? initialPosition})
: super(
fixtureDefs: [
FixtureDef(
CircleShape()..radius = 5,
restitution: 0.8,
friction: 0.4,
),
],
bodyDef: BodyDef(
angularDamping: 0.8,
position: initialPosition ?? Vector2.zero(),
type: BodyType.dynamic,
),
);
@override
void onTapDown(_) {
body.applyLinearImpulse(Vector2.random() * 5000);
}
}
class Wall extends BodyComponent {
final Vector2 _start;
final Vector2 _end;
Wall(this._start, this._end);
@override
Body createBody() {
final shape = EdgeShape()..set(_start, _end);
final fixtureDef = FixtureDef(shape, friction: 0.3);
final bodyDef = BodyDef(
position: Vector2.zero(),
);
return world.createBody(bodyDef)..createFixture(fixtureDef);
}
}
World是Flame引擎預設的一層Component,用來方便更細部的管理遊戲的內容,一般是可用可不用。
但在Forge2D中似乎預設必須要使用它來管理Component。
這裡頭比較棘手的是關於Forge2D中強制使用BodyComponent這個類別,也就是說之前設計的Component不能用....這就是為什麼我要將「paint」和「PositionComponent.render」分開設計的原因,因為「paint」所做的內容不一定只會在「PositionComponent.render」用到....所以,不棘手。
但Forge2D中,對於「尺寸」這件事情的觀念就很奇怪了!
之前的範例中,談到尺寸都是指「pixel」。
但在Forge2D中使用的尺寸怎麼看都不是pixel,畢竟「Ball」的半徑被直接宣告為「5」,(CircleShape()..radius = 5,)。
研究過後發現,原來這是種相對尺寸。
World會被賦予一個同樣也是相對的尺寸,例如如果World是100,而Ball是5,那World就是Ball的二十倍大。
在Forge2D這個系統中,幾乎所有使用到尺寸的地方,都被自動替換成使用這種相對尺寸,所以如果要在Ball裡面畫上一朵花,只需要這樣做....
class Ball extends BodyComponent with TapCallbacks {
FlowerPainter flowerPainter = FlowerPainter(0, false)..setCenter(0,0);
@override
void update(double dt) {
super.update(dt);
}
render(Canvas canvas) {
super.render(canvas);
flowerPainter.paint(canvas, Size(9, 9));
}
...
}
花的紋路還會隨著「Ball」的滾動而跟著旋轉。這物理引擎的威力未免太變態!(我喜歡!)
補充1:Forge2D一樣不能當Widget使用,必須要用GameWidget包裝。官方範例推薦的方法如下。
GameWidget.controlled(gameFactory: MyGame4.new)
補充2:在這個官方範例中,遊戲生成瞬間的視窗大小就是遊戲世界大小,但Ball的絕對大小卻依舊不變。也就是說這裡確實存在著某個「幾pixel就對應遊戲中的一個尺度單位」的換算公式,但好像不需要太深究。